////////////////////////////////////////////////////////////////////
// File:	MeshManager.h
// Purpose: Manages all meshes/skinned meshes/quads in game. By reusing
//			data that is already loaded in or loading in new data on the fly.
// Author:	Jay Baird
////////////////////////////////////////////////////////////////////

#ifndef MESHMANAGER_H
#define MESHMANAGER_H

#include "Mesh.h"
#include "TextureManager.h"
#include "..\..\src\Game Objects\AABB.h"
#include <vector>

#ifndef MAX_PATH
#define MAX_PATH 260
#endif

class MeshManager
{
public:
	struct meshInfo
	{
		vec3 scale;	// For vertex&normal scaling of the mesh when loaded, only used for that
		AABB boundingVol;
		char fileName[MAX_PATH];
		Mesh* pMesh;	// if null then not used else used

		meshInfo() : pMesh(0), scale(1.0f) { fileName[0] = '\0'; }
		meshInfo(char* _fileName) : pMesh(0), scale(1.0f)
		{ if(_fileName) strcpy_s(fileName, strlen(_fileName) + 1, _fileName); }
	};

private:
	Mesh m_quad;	// Main quad to be used for rendering and instancing
	std::vector<meshInfo> m_meshes;
	static MeshManager m_instance;

	void LoadSkinned(SkinnedMesh* pMesh, std::ifstream& fin);
	void BuildVBOS(Mesh* pMesh);

	MeshManager() { }
	~MeshManager() { }
	MeshManager(const MeshManager&);
	MeshManager& operator=(const MeshManager&);

	// Functions for easy-debugging creation
	////////////////////////
	int CreateSphere(float radius, unsigned divisons, const char* name = NULL);
	int CreateBlock(float width, float height, float depth, const char* name = NULL);
	///////////////////////

public:
	static MeshManager* GetInstance() { return &m_instance; }

	// Loads a new mesh from the file specified or reuses it if it has already been loaded
	// Ins: (fileName): the file to load must be a ".pam"
	//		(scale): scales the vertices & normals of the mesh to this
	//		(param): texture info to either REPEAT Texturing or CLAMP texturing
	// Outs: None
	// Return: index for the mesh
	int Load(const char* fileName, vec3 scale = vec3(1.0f), TextureParam param = CLAMP);
	int Load(const std::string& fileName, vec3 scale = vec3(1.0f), TextureParam param = CLAMP) 
	{ 
		return Load(fileName.c_str(), scale, param); 
	}

	// Loads a new mesh from the file specified or reuses it if it has already been loaded
	// Ins: (textureFile): loads a quad into the mesh manager and reuses it if there is already a quad with the same info passed in
	//		(param): texture info to either REPEAT Texturing or CLAMP texturing
	//		(texTilesX): how many times to tile the texture in the X
	//		(texTilesY): how many times to tile the texture in the Y
	// Outs: None
	// Return: index for the mesh
	int LoadQuad(const char* textureFile = NULL, TextureParam param = CLAMP, unsigned texTilesX = 1, unsigned texTilesY = 1);
	int LoadQuad(const std::string& textureFile = "", TextureParam param = CLAMP, unsigned texTilesX = 1, unsigned texTilesY = 1)
	{
		return LoadQuad(textureFile.c_str(), param, texTilesX, texTilesY);
	}

	// Remove a specified mesh and the memory associated with it from the mesh manager according to the ID
	// Ins: None
	// Outs: None
	// Return: returns true if the mesh was properly removed else false
	bool Remove(int id);

	// Removes all meshes in the meshmanager and cleans up memory associated with them
	// Ins: None
	// Outs: None
	// Return: None
	void RemoveAll();

	inline bool IsAvailable(int id) const
	{ 
		if(m_meshes.empty() || id < 0 || (unsigned)id >= (unsigned)m_meshes.size() || !m_meshes[id].pMesh)
			return false;
		return true;
	}

	inline MeshType GetMeshType(int id)
	{ 
		if(IsAvailable(id))
		{
			if(strcmp(m_meshes[id].fileName, "_QUAD") == 0) 
				return MESH_TYPE_QUAD;
			return m_meshes[id].pMesh->Type();
		}
		return MESH_TYPE_NULL;
	}

	inline const AABB* GetBV(int id)
	{
		if(IsAvailable(id))
			return &m_meshes[id].boundingVol;
		return NULL;
	}

	inline vec3 GetScale(int id)
	{
		if(IsAvailable(id))
			return m_meshes[id].scale;
		return vec3(1.0f);
	}

	inline const char* GetMeshFilename(int id)
	{
		if(IsAvailable(id))
			return m_meshes[id].fileName;
		return NULL;
	}

	inline unsigned GetMeshBufferID(int id, MeshComponent::MeshBufferType meshType)
	{
		if(IsAvailable(id))
			return m_meshes[id].pMesh->GetBufferID(meshType);
		return 0;
	}

	inline int GetTextureID(int id) const
	{
		if(IsAvailable(id))
			return m_meshes[id].pMesh->GetTextureID();
		return -1;
	}

	inline unsigned GetTriangleCount(int id) const
	{
		if(IsAvailable(id))
			return m_meshes[id].pMesh->GetTriangleCount();
		return 0;
	}

	inline unsigned GetVertexCount(int id) const
	{
		if(IsAvailable(id))
			return m_meshes[id].pMesh->GetVertexCount();
		return 0;
	}

	inline unsigned GetBoneCount(int id)
	{
		if(GetMeshType(id) == MESH_TYPE_SKINNED_MESH)
			return ((SkinnedMesh*)m_meshes[id].pMesh)->GetBoneCount();
		return 0;
	}

	// Mesh
	inline const MeshComponent::Triangle* GetTriangles(int id) const
	{ 
		if(IsAvailable(id)) 
			return m_meshes[id].pMesh->GetTriangles(); 
		return NULL; 
	}

	inline const vec3* GetVertices(int id) const
	{ 
		if(IsAvailable(id)) 
			return m_meshes[id].pMesh->GetVertices();  
		return NULL; 
	}

	inline const vec3* GetNormals(int id) const
	{ 
		if(IsAvailable(id))
			return m_meshes[id].pMesh->GetNormals();   
		return NULL; 
	}

	inline const vec2* GetTexCoords(int id) const 
	{ 
		if(IsAvailable(id)) 
			return m_meshes[id].pMesh->GetTexCoords(); 
		return NULL; 
	}

	// SkinnedMesh
	inline const MeshComponent::Bone* GetBones(int id)
	{
		if(GetMeshType(id) == MESH_TYPE_SKINNED_MESH)
			return ((SkinnedMesh*)m_meshes[id].pMesh)->GetBones();
		return NULL;
	}

	inline const std::vector<std::vector<MeshComponent::Influence>>* GetInfluences(int id)
	{
		if(GetMeshType(id) == MESH_TYPE_SKINNED_MESH)
			return ((SkinnedMesh*)m_meshes[id].pMesh)->GetInfluences();
		return NULL;
	}
};

#endif